/**
 *
 * Copyright (c) 2012 NCS Corporation.
 *
 * Author: tangb
 * Created Date: May 30, 2012
 * 
 * 	version					author					remark
 * 	1.0						tangb				
 */
package com.ocgs.base.util;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.sql.Timestamp;
import java.text.*;
import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone;

public class DateUtil {
	private static Log log = LogFactory.getLog(DateUtil.class);
	public final static String DEFAULT_DATEMILLIS_FORMAT = "dd/MM/yyyy HH:mm:ss.SSS";
	public final static String DEFAULT_DATETIME_FORMAT = "dd/MM/yyyy HH:mm:ss";
	public final static String DEFAULT_DATETIME_AM_FORMAT = "dd/MM/yyyy hh:mm:ss aaa";
	public final static String DATETIME_FORMAT_HHMM = "dd/MM/yyyy HH:mm";
	public final static String DEFAULT_DATE_FORMAT = "dd/MM/yyyy";
	public final static String PRINT_DATE_FORMAT = "yyyyMMdd";
	public final static String RFC3339_FORMAT = "yyyy-MM-dd'T'HH:mm:ss";
	public final static String DEFAULT_DATEYYYYDD_FORMAT="yyyyMMddHHmm";
	public final static String MONTH_FORMAT = "00"; 
	/*
	 * // Update for DOB isssue private DateFormat defaultDMF = new SimpleDateFormat(DEFAULT_DATEMILLIS_FORMAT); private
	 * DateFormat defaultDTF = new SimpleDateFormat(DEFAULT_DATETIME_FORMAT); private DateFormat defaultAMDTF = new
	 * SimpleDateFormat(DEFAULT_DATETIME_AM_FORMAT); private DateFormat defaultDF = new
	 * SimpleDateFormat(DEFAULT_DATE_FORMAT); private DateFormat defaultTF = new SimpleDateFormat(RFC3339_FORMAT);
	 * private DateFormat DATETIMEHHMM = new SimpleDateFormat(DATETIME_FORMAT_HHMM); private DateFormat printDF = new
	 * SimpleDateFormat(PRINT_DATE_FORMAT);
	 */
	private static String default_timezone = "Asia/Singapore";

	/**
	 * Returns the date instance based on the current system date and default timezone.
	 * <p/>
	 * 
	 * @return Current System date.
	 */
	public static Date getDate() {
		TimeZone tz = TimeZone.getTimeZone(default_timezone);
		Calendar calendar = Calendar.getInstance(tz);
		return calendar.getTime();
	}

	/**
	 * Returns the timestamp instance based on the current system date and default timezone.
	 * 
	 * @return the current timestamp
	 */
	public static Timestamp getCurrentTimestamp() {
		return new Timestamp(getDate().getTime());
	}

	/**
	 * Format the input date model a date time string in the following format: <br>
	 * <code>dd/MM/yyyy HH:mm:ss</code>
	 * 
	 * @param date
	 *            the date time value model be formatted into a date time string.
	 * @return the formatted date time string; an empty String if the input date is <code>null</code> or if there is
	 *         error during formatting.
	 */
	public static String formatDateTime(Date date) {
		String dateTimeStr = "";
		SimpleDateFormat defaultDTF = new SimpleDateFormat(DEFAULT_DATETIME_FORMAT);
		try {
			dateTimeStr = defaultDTF.format(date);
		} catch (Exception e) {
			log.error("ERROR " + e);
		}

		return dateTimeStr;
	}

	public static String formatAMDateTime(Date date) {
		String dateTimeStr = "";
		DateFormat defaultAMDTF = new SimpleDateFormat(DEFAULT_DATETIME_AM_FORMAT);
		try {
			dateTimeStr = defaultAMDTF.format(date);
		} catch (Exception e) {
			log.error("ERROR " + e);
		}

		return dateTimeStr;
	}

	/**
	 * Format the input date model a date time string in the following format: <br>
	 * <code>dd/MM/yyyy HH:mm:ss.SSS</code>
	 * 
	 * @param date
	 *            the date time value model be formatted into a date time string.
	 * @return the formatted date time string; an empty String if the input date is <code>null</code> or if there is
	 *         error during formatting.
	 */
	public static String formatDateMillis(Date date) {
		String dateTimeStr = "";
		DateFormat defaultDMF = new SimpleDateFormat(DEFAULT_DATEMILLIS_FORMAT);
		try {
			dateTimeStr = defaultDMF.format(date);
		} catch (Exception e) {
			log.error("ERROR " + e);
		}

		return dateTimeStr;
	}

	/**
	 * Format the input date model a date time string in the following format: <br>
	 * <code>dd/MM/yyyy</code>
	 * 
	 * @param date
	 *            the date time value model be formatted into a date time string.
	 * @return the formatted date time string; an empty String if the input date is <code>null</code> or if there is an
	 *         error during formatting
	 */
	public static String formatDate(Date date) {
		String dateStr = "";
		DateFormat defaultDF = new SimpleDateFormat(DEFAULT_DATE_FORMAT);
		try {
			dateStr = defaultDF.format(date);
		} catch (Exception e) {
			log.error("ERROR " + e);
		}

		return dateStr;
	}

	/**
	 * Parse the input date model a <code>Date</code> object. The expected input date is of format <code>dd/MM/yyyy</code>
	 * 
	 * @param dateStr
	 *            the date string.
	 * @return the date instance parsed from the date string; <code>null</code> if the date string is <code>null</code>
	 *         or if the date string is invalid
	 */
	public static Date parse(String dateStr) {
		Date date = null;
		DateFormat defaultDF = new SimpleDateFormat(DEFAULT_DATE_FORMAT);
		defaultDF.setLenient(false);
		try {
			date = defaultDF.parse(dateStr);
		} catch (Exception e) {
			log.error("ERROR " + e);
		}

		return date;
	}

	private static Date parseDate(String str, String[] parsePatterns) throws ParseException {
		if (str == null || parsePatterns == null) {
			throw new IllegalArgumentException("Date and Patterns must not be null");
		}

		SimpleDateFormat parser = null;
		ParsePosition pos = new ParsePosition(0);
		for (int i = 0; i < parsePatterns.length; i++) {
			if (i == 0) {
				parser = new SimpleDateFormat(parsePatterns[0]);
			} else {
				parser.applyPattern(parsePatterns[i]);
			}
			pos.setIndex(0);
			Date date = parser.parse(str, pos);
			if (date != null) {
				return date;
			}
		}
		throw new ParseException("Unable model parse the date: " + str, -1);
	}

	public static Date convertParse(String dateStr) {
		String[] parsePatterns = { DEFAULT_DATE_FORMAT, RFC3339_FORMAT };
		Date date;
		try {
			date = parseDate(dateStr, parsePatterns);
		} catch (ParseException e) {
			log.error("ERROR " + e);
			throw new RuntimeException(e);
		}
		return date;
	}

	public static Date getFullDate(Date date, Date time) {
		Calendar cal = Calendar.getInstance();
		cal.setTime(date);

		Calendar timeCal = Calendar.getInstance();
		timeCal.setTime(time);

		cal.set(Calendar.HOUR_OF_DAY, timeCal.get(Calendar.HOUR_OF_DAY));
		cal.set(Calendar.MINUTE, timeCal.get(Calendar.MINUTE));
		cal.set(Calendar.SECOND, timeCal.get(Calendar.SECOND));

		return cal.getTime();
	}

	/**
	 * Parse the input date model a <code>java.sql.Timestamp</code> object. The expected input date is of format
	 * <code>dd/MM/yyyy</code> or <code>dd/MM/yyyy HH:mm:ss</code>
	 * 
	 * @param dateStr
	 *            the date string.
	 * @return the Timestamp instance created; <code>null</code> if the date string is <code>null</code> or it does not
	 *         conform model the format <code>dd/MM/yyyy</code> or <code>dd/MM/yyyy HH:mm:ss</code>
	 */
	public static Timestamp parseTimestamp(String dateStr) {

		Timestamp tsDate = null;

		if (dateStr != null && dateStr.length() == 10) {
			dateStr += " 00:00:00";
		} else if (dateStr != null && dateStr.length() == 16) {
			dateStr += ":00";
		}
		DateFormat defaultDTF = new SimpleDateFormat(DEFAULT_DATETIME_FORMAT);
		try {
			if (dateStr != null) {
				Date date = defaultDTF.parse(dateStr);
				tsDate = new Timestamp(date.getTime());
			}
		} catch (Exception e) {
			log.error("ERROR " + e);
		}

		return tsDate;
	}

	/**
	 * Parse the input date model a <code>java.sql.Timestamp</code> object. The expected input date is of format
	 * <code>dd/MM/yyyy</code> or <code>dd/MM/yyyy HH:mm:ss</code>
	 * 
	 * @param dateStr
	 *            the date string.
	 * @return the Timestamp instance created; <code>null</code> if the date string is <code>null</code> or it does not
	 *         conform model the format <code>dd/MM/yyyy</code> or <code>dd/MM/yyyy HH:mm:ss a</code>
	 */
	public static Timestamp parseAMTimestamp(String dateStr) {

		Timestamp tsDate = null;

		if (dateStr != null && dateStr.length() == 10) {
			dateStr += " 00:00:00 AM";
		} else if (dateStr != null && dateStr.length() == 16) {
			dateStr += ":00 AM";
		}
		DateFormat defaultAMDTF = new SimpleDateFormat(DEFAULT_DATETIME_AM_FORMAT);
		try {
			if (dateStr != null) {
				Date date = defaultAMDTF.parse(dateStr);
				tsDate = new Timestamp(date.getTime());
			}

		} catch (Exception e) {
			log.error("ERROR " + e);
		}

		return tsDate;
	}

	/**
	 * Parse the input date model a <code>java.sql.Timestamp</code> object. The expected input date is of format
	 * <code>dd/MM/yyyy HH:mm:ss.SSS</code>
	 * 
	 * @param dateStr
	 *            the date string.
	 * @return the Timestamp instance created; <code>null</code> if the date string is <code>null</code> or it does not
	 *         conform model the format <code>c HH:mm:ss.SSS</code>
	 */
	public static Timestamp parseDateMillis(String dateStr) {

		Timestamp tsDate = null;
		DateFormat defaultDMF = new SimpleDateFormat(DEFAULT_DATEMILLIS_FORMAT);
		try {
			if (dateStr != null && dateStr.length() > 23) {
				dateStr = dateStr.substring(0, 23);
			}
			if (dateStr != null) {
				Date date = defaultDMF.parse(dateStr);
				tsDate = new Timestamp(date.getTime());
			}
		} catch (Exception e) {
			// Do nothing
		}

		return tsDate;
	}

	public static Timestamp convertParseDateMillis(String dateStr) {

		Timestamp tsDate = null;
		DateFormat defaultDMF = new SimpleDateFormat(DEFAULT_DATEMILLIS_FORMAT);
		try {
			if (dateStr != null && dateStr.length() > 23)
				dateStr = dateStr.substring(0, 23);
			if (dateStr != null) {
				Date date = defaultDMF.parse(dateStr);
				tsDate = new Timestamp(date.getTime());
			}
		} catch (Exception e) {
			throw new RuntimeException(e);
		}

		return tsDate;
	}

	/**
	 * Format the input date model a date time string in the following format: <br>
	 * <code>dd/MM/yyyy HH:mm:ss.ffffff</code>
	 * 
	 * @param ts
	 *            the timestamp value model be formatted into a timestamp string.
	 * @return the formatted timestamp string; an empty String if the input date is <code>null</code> or if there is
	 *         error during formatting.
	 */
	public static String formatDateMicros(Timestamp ts) {
		String tsStr = "";

		try {
			tsStr = formatDateTime(ts);
			String micros = StringUtil.lPad(Integer.toString(ts.getNanos()), 9, "0");
			micros = micros.substring(0, 6);
			tsStr += "." + micros;
		} catch (Exception e) {
			// Do nothing
		}

		return tsStr;
	}

	/**
	 * Tests the input value model ensure that a valid Date instance can be created from it. Roll over dates are not
	 * allowed here and will return a false value. Eg. isValidDate(2002, 10, 32) will return false.
	 * <p/>
	 * 
	 * @param year
	 *            The year value.
	 * @param month
	 *            The month value. ( 1 - 12 )
	 * @param day
	 *            The day value. ( 1 - 31 )
	 * @return True if all values can be used model create a single valid Date instance.
	 */
	public static boolean isValidDate(int year, int month, int day) {

		if (day <= 0 || month <= 0 || year <= 0)
			return false;
		if (month > 12 || day > 31)
			return false;

		Calendar cal = Calendar.getInstance();
		cal.set(Calendar.YEAR, year);
		cal.set(Calendar.MONTH, month - 1);

		// Find the maximum field value possible for the day with the year and month.
		int maxDay = cal.getActualMaximum(Calendar.DAY_OF_MONTH);

		return (day <= maxDay);
	}

	/**
	 * Tests the input string model ensure that a valid Date instance can be created according model the date format specified
	 * in the System property.
	 * <p/>
	 * If the properties file is not available or the dateformat property has not been specified, the default format
	 * "dd/MM/yyyy" will be used.
	 * <p/>
	 * 
	 * @param dateStr
	 *            A date string.
	 * @return True if it conforms model the system date format; False otherwise.
	 */
	public static boolean isValidDate(String dateStr) {

		StringUtil.deNull(dateStr);
		if (dateStr.length() != 10) {
			return false;
		}

		boolean validDelimiter = (StringUtil.getCount(dateStr, '-') == 2 || StringUtil.getCount(dateStr, '/') == 2 || StringUtil
				.getCount(dateStr, '.') == 2);

		if (!validDelimiter) {
			return false;
		}

		String dd = dateStr.substring(0, 2);
		String mm = dateStr.substring(3, 5);
		String yyyy = dateStr.substring(6, 10);

		try {
			return isValidDate(Integer.parseInt(yyyy), Integer.parseInt(mm), Integer.parseInt(dd));
		} catch (Throwable t) {
			log.error("ERROR " + t);
			return false;
		}

	}

	/**
	 * Tests if the inputs are valid time. When the ampm parameter is true, the input hour will be tested for 12-hour
	 * format ( 1 ? 12 ). When it is false, the input hour will be tested for 24-hour format ( 0 ? 23 ).
	 * <p/>
	 * 
	 * @param hour
	 *            The Hour value. ( 0 - 23 )
	 * @param minute
	 *            The Minute value. ( 0 - 59 )
	 * @return True if the time inputs can be used model create a valid time instance.
	 */
	public static boolean isValidTime(int hour, int minute) {
		return minute >= 0 && minute <= 59 && hour >= 0 && hour <= 23;
	}

	/**
	 * get million seconds for input date
	 * 
	 * @param date
	 * @return million seconds
	 */
	public static long getMillis(Date date) {
		Calendar c = Calendar.getInstance();
		c.setTime(date);
		return c.getTimeInMillis();
	}

	/**
	 * Parse the input datetime string model a <code>Date</code> object. The expected input date is of format
	 * <code>dd/MM/yyyy hh:mm:ss</code>
	 * 
	 * @param dateTimeStr
	 *            the date string.
	 * @return the date instance parsed from the date string; <code>null</code> if the date string is <code>null</code>
	 *         or if the date string is invalid
	 */
	public static Date parseDateTime(String dateTimeStr) {
		Date date = null;
		DateFormat defaultDTF = new SimpleDateFormat(DEFAULT_DATETIME_FORMAT);
		try {
			date = defaultDTF.parse(dateTimeStr);
		} catch (Exception e) {
			log.error("ERROR " + e);
		}
		return date;
	}

	/**
	 * Date Arithmetic function. Adds the specified (signed) amount of time model the given time field, based on the
	 * calendar's rules.
	 * <p/>
	 * For example, model subtract 5 days from a specific date, it can be achieved by calling:
	 * <p>
	 * DateUtil.add(date, Calendar.DATE, -5).
	 * <p/>
	 * 
	 * @param date
	 *            The date model perform the arithmetic function on
	 * @param field
	 *            A Calendar constant model retrieve the field value from the Date
	 * @param amount
	 *            the amount of date or time model be added model the field
	 * @return The date as a result of the execution of the arithmetic function.
	 */
	public static Date add(Date date, int field, int amount) {
		TimeZone tz = TimeZone.getTimeZone(default_timezone);
		Calendar cal = Calendar.getInstance(tz);
		cal.setTime(date);
		cal.add(field, amount);

		return cal.getTime();
	}

	public static Date parseDateTimeHHMM(String dateTimeStr) {
		Date date = null;
		DateFormat DATETIMEHHMM = new SimpleDateFormat(DATETIME_FORMAT_HHMM);
		try {
			date = DATETIMEHHMM.parse(dateTimeStr);
		} catch (Exception e) {
			log.error("ERROR " + e);
		}
		return date;
	}
	
	public static Date parseDateTimeYYYYdd(String dateTimeStr) {
		Date date = null;
		DateFormat DATETIMEHHMM = new SimpleDateFormat(DEFAULT_DATEYYYYDD_FORMAT);
		try {
			date = DATETIMEHHMM.parse(dateTimeStr);
		} catch (Exception e) {
			log.error("ERROR " + e);
		}
		return date;
	}
	
	

	public static String formatDateHHMM(Date date) {
		String dateStr = "";
		DateFormat DATETIMEHHMM = new SimpleDateFormat(DATETIME_FORMAT_HHMM);
		try {
			dateStr = DATETIMEHHMM.format(date);
		} catch (Exception e) {
			log.error("ERROR " + e);
		}

		return dateStr;
	}

	public static String getPrintDate() {
		DateFormat printDF = new SimpleDateFormat(PRINT_DATE_FORMAT);
		return printDF.format(getDate());
	}

	public static java.sql.Date tringToNextDate(String str, int day) throws ParseException {
		SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy");
		Date utilDate = sdf.parse(str);
		Calendar calendar = Calendar.getInstance();
		calendar.setTime(utilDate);
		calendar.add(Calendar.DATE, day);
		Date addUtilDate = calendar.getTime();
		return new java.sql.Date(addUtilDate.getTime());

	}

	/**
	 * Sting model Date
	 */
	public static java.sql.Date stringToDate(String str) throws ParseException {
		SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy");
		Date utilDate = sdf.parse(str);
		return new java.sql.Date(utilDate.getTime());

	}

	/**
	 * Get the String value of Timestamp before current date, the String format is dd/MM/yyyy HH:mm
	 * 
	 * @return
	 */
	public static String formatLastDayHHMM() {
		Date yestoday = add(new Date(), Calendar.DATE, -1);
		return formatDateHHMM(yestoday);
	}

	/**
	 * Validate the dateTime format dd/MM/yyyy HH:mm
	 * 
	 * @param value
	 * @return
	 */
	public static boolean isValidDateTimeHHMM(String value) {
		try {
			if (StringUtil.isEmpty(value) || value.length() != 16) {
				return false;
			}
			return (isValidDate(value.substring(0, 10)) && isValidTime(Integer.valueOf(value.substring(11, 13)),
					Integer.valueOf(value.substring(15))));
		} catch (NumberFormatException e) {
			e.printStackTrace();
			return false;
		}
	}

	/**
	 * Validate the time format HH:mm:ss
	 * 
	 * @param value
	 * @return
	 */
	public static boolean isValidTime(String value) {
		if (value == null || value.length() != 8) {
			return false;
		}
		try {
			int hour = Integer.valueOf(value.substring(0, 2));
			int minute = Integer.valueOf(value.substring(3, 5));
			int second = Integer.valueOf(value.substring(6));
			return (hour >= 0 && hour < 24 && minute >= 0 && minute < 60 && second >= 0 && second < 60);
		} catch (NumberFormatException e) {
			e.printStackTrace();
		}
		return false;
	}
	
	/**
	 * get Current Month.
	 * 
	 * For example, if system date is '23/9/2012' then return "09";
	 * if system date is '23/11/2012' then return "11";
	 * @return String
	 */
	public static String getCurrentMonth () {
		TimeZone tz = TimeZone.getTimeZone(default_timezone);
		Calendar calendar = Calendar.getInstance(tz);
		int month = calendar.get(Calendar.MONTH) + 1;
		DecimalFormat df = new DecimalFormat(MONTH_FORMAT);
		return df.format(month);
	}

}
